import std.stdio;
import std.array;
import std.process;
import std.string;
import std.c.linux.linux;
import std.conv;
import std.math;
import std.regexp;
import std.algorithm;
import std.random;

import img_io;
/+

dmd -O -release -inline -m64 graph.d

+/


struct XY{ int x; int y; }

struct RegCmp{
 	Regions owner;
  	int regId;
  	int size;
  	int borderAvgDist;
  	string toString(){
  		return "" ~ to!string(borderAvgDist) ~ "-" ~to!string(size);
  	}
  	
  	void paint(Pixel)(ImgData!Pixel img, Pixel color){
  		foreach(int i,ref Pixel b; img.data){
  			if( owner.regMap[i]==regId ){
  				b = color;
		  	}
  		}
  	}
  	
  	void putPixelOffsets(ref bool[int] set){
  		foreach(int i, int v; owner.regMap){
  			if( v == regId ){
  				set[i] = true;
		  	}
  		}
  	} 
  	
  	bool hasAnyPixelOffset( ref bool[int] set ){
  		foreach(int i, int v; owner.regMap){
  			if( v == regId ){
  				if( i in set ){
  					return true;
  				}
			}
  		}
		return false;  		  		
  	}  
}

struct Regions{
	int[] regMap;
	RegCmp[] regCmps;
	
	this(ImgData!ubyte d, ubyte delegate(int x, int y) distance){
		regMap  = new int[d.data.length];
		int regId=1;
		int w = d.w;
		int h = d.h;
		foreach(x; 0..w){
			foreach(y; 0..h){
				if( regMap[x + y*w]==0 && distance(x,y)==0 ){
					regMap[x + y*w] = regId;
					
					// enlarge region
					int borderPoints=0;
					int totalBorderDist=0;
					
					int regSize=1;
					XY[] regionBorder;
					regionBorder ~= XY(x,y);
					while(regionBorder.length>0){
						XY[] regionBorderNext;						
						foreach(xy; regionBorder){
							foreach(dx; -1..2){
								foreach(dy; -1..2){
									int xx = xy.x+dx;
									int yy = xy.y+dy;
									if(xx>=0 && yy>=0 && xx<w && yy<h && regMap[xx + yy*w]==0 ){
										ubyte dist = distance(xx,yy);
										if( dist==0 ){
										  // new adjacent point in region
										  regMap[xx + yy*w] = regId;
										  regionBorderNext ~= XY(xx,yy);
										  regSize++;
									    }else{
										   // border point - not in regions
										   totalBorderDist += dist;
										   borderPoints ++;	   	
									    }
									} 
								}
							}							
						}
						regionBorder = regionBorderNext;
					}
					
					int avgDist = borderPoints==0 ? -1 : totalBorderDist / borderPoints;
					if( regSize <= w*h/2 && regSize>3 ){
						regCmps ~= RegCmp(this,regId,regSize,avgDist);
					    writeln(`regid `, regId,  
					  	` regsize `,regSize, ` avgDist `,avgDist);
				    }
					regId++;
				}
			}
		}
		//writeln(regId, ` - regId`);		
	}
}

T median(T)(T[] data){
	if( data.length==1 ){
		return data[0];
	}
	T[] dcopy = data.dup;
	sort(data);
	return data[$/2];	
}



auto fautoret(int i, long j){
	return i+j;
}

int randomColor(){
	return uniform(0, 0x01_00_00_00);
}

void graph_main(){
	long iaa = fautoret(10,20);
	
  writeln("pwd ", shell("pwd"));
  
  //ImgData!ubyte img = readImg("../priv/q-man-gray.jpg");
  ImgData!ubyte img = readImg("/opt/lett.png");
  
  writeln(img.w,"  x  ",img.h," size=",img.data.length);
  //if (img==null ) writeln("struct null");

  ImgData!ubyte imgLow = img.scaleDown(2);
  imgLow.saveScaledUp("/tmp/upLow.png",10);
  
  RegCmp[] regCmps;
  foreach(limit; 0..255){
  	writeln("region color higher ",limit);
	  Regions regs =  Regions(imgLow, delegate ubyte(int x,int y){
    			if( imgLow.val(x,y) >= limit ){
    				return 0;
    			}else{
    				return cast(ubyte)(limit - imgLow.val(x,y));
    			} 
    	});
	  regCmps ~= regs.regCmps;
  }
  foreach(limit; 0..255){
  	writeln("region color lower ",limit);
	  Regions regs =  Regions(imgLow, delegate ubyte(int x,int y){
    			if( imgLow.val(x,y) <= limit ){
    				return 0;
    			}else{
    				return cast(ubyte)(imgLow.val(x,y)-limit);
    			} 
    	});
	  regCmps ~= regs.regCmps;
  }
  //sort!"a.borderAvgDist > b.borderAvgDist"(regCmps);
  //sort!"a.size > b.size"(regCmps);
  sort!"1.0*a.borderAvgDist*a.borderAvgDist * a.size > 1.0*b.borderAvgDist*b.borderAvgDist * b.size"(regCmps);
  writeln("regCmps len ", regCmps.length);
  writeln("regCmps ", regCmps);

/*
  Regions regions =  Regions(imgLow, delegate bool(int x,int y){
    		return imgLow.val(x,y) >= 155; 
    });
  */

  ImgData!int imgLowRegColor = imgLow.asRGB();
  //bestRegCmp.paint(imgLowReg,cast(ubyte)255);
  //imgLowReg.saveScaledUp("/tmp/upLowReg.png",10);

  bool[int] markedPixelIndexes;
  
  foreach(i; 0..regCmps.length){
	RegCmp bestRegCmp = regCmps[i];
  
  	//bestRegCmp.paint(imgLowRegColor, 0xff0000);
  	if( ! bestRegCmp.hasAnyPixelOffset(markedPixelIndexes) ){
  	  bestRegCmp.paint(imgLowRegColor, randomColor());
  	  bestRegCmp.putPixelOffsets( markedPixelIndexes );
  	  writeln("painted region ",i," ",bestRegCmp);
  	}else{
  		//writeln("skipped region ",i);
  	}
  	//writeln("i ",i," marked pixels ", markedPixelIndexes.length);
  	
  }

  imgLowRegColor.saveScaledUp("/tmp/upLowReg.png",10);
  
   //oldAttention(img);
   writeln("finished");
}

void oldAttention(ImgData!ubyte img){
  //printAttentionLinePiece(img, 200);


  ubyte[][] attentionImg;   
  foreach(YLINE; 0..img.h){
  	  attentionImg ~= new ubyte[img.w];
	  ubyte[] lineData;
	  foreach(x; 0..500){
	  	lineData ~= img.val(x,YLINE);
	  }
	  ubyte splitPoint = lineSplit(lineData);
	  foreach(x; 0..500){
	  	ubyte b = img.val(x,YLINE);
	  	if( b > splitPoint ){
	  		attentionImg[YLINE][x] = 255;
	  	}else{
	  		attentionImg[YLINE][x] = b;
	  	}
	  }
  }
    mkGrayImage("/tmp/man-gray-attention.png",img.w,img.h, delegate ubyte(int x,int y){
    		return attentionImg[y][x]; 
    });


//  execvp_with_input("grep",["grep","1"],['1','2','3','a']);

//   execvp_with_input("convert",["convert","-size","3x2","-depth","8","gray:-","fromD.png"],cast(byte[])[0,0,0,255,128,128]);

  //mkGrayImage("fromD.png",200,100, function ubyte(int x,int y){ return cast(ubyte)(220-abs(x-100)); });
//  mkGrayImage("2fromD.png",200,100, function ubyte(int x,int y){ 
//	int dist = cast(int)(sqrt((x-100)*(x-100)+(y-50)*(y-50)));
//	return cast(ubyte)(220-abs(dist-30)); 
//  });
	
	//makeMedianImg(img);
	
}

void printAttentionLinePiece(ImgData!ubyte img, int YLINE){
	  ubyte[] lineData;
  foreach(x; 0..500){
  	//writeln(x,' ',img.val(x,YLINE));
  	lineData ~= img.val(x,YLINE);
  }
  writeln(`avg `,avg(lineData));
  ubyte splitPoint = lineSplit(lineData);
  writeln(`lineSplit `, splitPoint);
  foreach(x; 0..500){
  	ubyte b = img.val(x,YLINE);
  	writeln(x,' ',b, ' ',  b > splitPoint ? '*' : ' ');
  }

}

void makeMedianImg(ImgData!ubyte img){
    mkGrayImage("man-median.png",img.w,img.h, delegate ubyte(int x,int y){ 
      byte v = median(img.circleVals(x,y,10));
  	  return cast(ubyte)(v); 
    });
}

double avg(ubyte[] data){
	double sum=0;
	foreach(b; data){
		sum += b;
	}
	return sum / data.length;
}

double perPointAvgSquaredDiff(ubyte[] data){
	double avg=avg(data);
	double sqDiff = 0;
	foreach(b; data){
		sqDiff += (avg-b)*(avg-b);
	}
	return sqDiff / data.length;
}

ubyte lineSplit(ubyte[] data){
	ubyte bestSplit=255;
	double minDiff=-1;
	foreach(ubyte div; 0..255){
		ubyte[] low, high;
		foreach(b; data){
			if( b > div ){
				high ~= b;
			}else{
				low ~= b;
			}
		}
		if( high.length>0 && low.length>0 ){
			double diff = max( perPointAvgSquaredDiff(low), perPointAvgSquaredDiff(high) );
			if( minDiff==-1 || minDiff>diff ){
				minDiff=diff;
				bestSplit=div;
			}
		}
	}
	return bestSplit;
}
